package casper.alpha;

import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;

import casper.alpha.utils.CasperDataGenerator;
import casper.alpha.utils.CasperProfiler;
import casper.alpha.utils.CasperQuery;
import casper.alpha.utils.CasperUtil;

import com.infomatiq.jsi.Rectangle;
import com.infomatiq.jsi.rtree.Node;
import com.infomatiq.jsi.rtree.RTree;
import com.infomatiq.jsi.test.SpatialIndexFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineSegment;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.util.GeometricShapeFactory;

public class CasperSearch {
	
	public void search(Coordinate exact, String lineString, int k, int t, boolean pub, boolean improve, double epsilon) throws ParseException {
		
		// clear cache to have precise profile for each search
		new CasperQuery().clearDataCache();
		CasperManager.setK(k);
		
		long tCasperSearch = 0;
		long tFilter = 0;
		//Geometry geometry = new WKTReader().read("LINESTRING ("+lineString+")");
		Geometry geometry = new WKTReader().read(lineString);
//		System.out.println("o: " + geometry.getCoordinates().length);
		if(improve) {
			if (!pub) {
//				geometry = geometry.convexHull();
			}
			geometry = CasperUtil.reduceGeometry(geometry, epsilon);
//			System.out.println("r: " + geometry.getCoordinates().length);
		}
		
		long tStart = System.currentTimeMillis();
		
		Queue<LineSegment> lineSegmentQueue = new LinkedList<LineSegment>();
		
		for(int i = 0; i < geometry.getCoordinates().length - 1; i++) {
			LineSegment lineSegment = null;
			lineSegment = new LineSegment(geometry.getCoordinates()[i], geometry.getCoordinates()[i+1]);
			lineSegmentQueue.add(lineSegment);
		}
		
		int elapsed = 0;
		if(pub) {
			List<Coordinate> result = new LinkedList<Coordinate>();
			result.addAll(new CasperQuery().getAllPublicDataInGeometry(geometry));
//			System.out.println(result.size());
			while(lineSegmentQueue.size() > 0) {
				// main loop until queue is empty
				
				LineSegment segment = lineSegmentQueue.poll();
				
				if(segment == null) {
					// empty queue ???
					break;
				}
				
				// runtime control
				elapsed ++;
				
				Coordinate c0 = segment.getCoordinate(0);
				Coordinate c1 = segment.getCoordinate(1);
				//System.out.println("edge " + segment);
				// public data search
				List<Coordinate> c0knns = new CasperQuery().getPublicKNNs(c0);
				List<Coordinate> c1knns = new CasperQuery().getPublicKNNs(c1);
				
				// first object in knns list is the kth
				Coordinate c0kth = c0knns.get(c0knns.size() - 1);
				Coordinate c1kth = c1knns.get(c1knns.size() - 1);
				//////System.out.println(String.format("c0kth : %f, %f", c0kth.x, c0kth.y));
				//////System.out.println(String.format("c1kth : %f, %f", c1kth.x, c1kth.y));
//				System.out.println("c0kth " + c0kth + " d " + c0.distance(c0kth));
//				System.out.println("c1kth " + c1kth + " d " + c1.distance(c1kth));
				if(c0kth.equals(c1kth)) {
					// trivial edge condition
					if(!result.contains(c0kth))
						result.add(c0kth);
//					System.out.println("trivial edge");
//					System.out.println("te "+result.size());
				} else {
					//System.out.println("PS");
					LineSegment l = new LineSegment(c0kth, c1kth);
					Coordinate c = CasperUtil.perpendicularBisector(l, segment);
					if(c!= null) {
						//System.out.println(String.format("c : %f, %f", c.x, c.y));
						//System.out.println("p " + c);
						//System.out.println("d1 = " + c.distance(c0kth));
						//System.out.println("d2 = " + c.distance(c1kth));
						if(elapsed + lineSegmentQueue.size() > t
								|| c.equals(c0) ||  c.equals(c1)) {
//							System.out.println("Try to terminate");
							// terminate search and return range search because cannot add more line segments to process
//							result.addAll(new CasperQuery().getAllPublicData(c, c.distance(c0kth) + epsilon));
//							System.out.println("ps "+result.size());
							result.addAll(new CasperQuery().getAllPublicData(c, c.distance(c0kth) + 0));
						} else {
							// add 2 segments
							// TODO: consider the order of c0, c, c1 ???
							////System.out.println("recursive refinement with " + c0 + "," + c + "," + c1);
							lineSegmentQueue.add(new LineSegment(c0, c));
							lineSegmentQueue.add(new LineSegment(c, c1));
						}
					} else {
						//System.out.println("perpendicular is null ???");
					}
				}
			}
			tCasperSearch = System.currentTimeMillis();
			Set<Coordinate> hsc = new HashSet<Coordinate>();
			hsc.addAll(result);
			result.clear();
			result.addAll(hsc);
			CasperProfiler.setAobjectCount(result.size());
//			System.out.println("size = " + result.size());
			
			//TODO: filter base on exact
			Coordinate best = null;
			double min = 999999999;
			for(Coordinate c : result) {
				double distance = Math.pow(c.x - exact.x, 2.0) + Math.pow(c.y - exact.y, 2.0);
				//System.out.println(String.format("POINT: %f * %f : %f", c.x, c.y, distance));
				if(distance < min) {
					min = distance;
					best = c;
				}
			}
			tFilter = System.currentTimeMillis();
			
			//System.out.println(String.format("BEST: %f * %f", best.x, best.y));
			
		} else {
			
			List<Integer> resultIds = new LinkedList<Integer>();
			resultIds.addAll(new CasperQuery().getAllPrivateDataIDInGeometry(geometry, improve));
			
			
			while(lineSegmentQueue.size() > 0) {
				// main loop until queue is empty
				
				LineSegment segment = lineSegmentQueue.poll();
				
				if(segment == null) {
					// empty queue ???
					break;
				}
//				//System.out.println(segment);
				// runtime control
				elapsed ++;
				
				Coordinate c0 = segment.getCoordinate(0);
				Coordinate c1 = segment.getCoordinate(1);
					
				// private data search
				List<Integer> c0knnids = new CasperQuery().getPrivateKNNIDs(c0, improve);
				List<Integer> c1knnids = new CasperQuery().getPrivateKNNIDs(c1, improve);
				// first object in knns list is the kth
				int c0kthid = c0knnids.get(c0knnids.size() - 1);
				int c1kthid = c1knnids.get(c0knnids.size() - 1);
				// System.out.println(c0kthid + ", " + c1kthid);
				Coordinate mc0kth = new CasperQuery().getMaxDistanceCoordinate(c0, new CasperQuery().getGeometryById(c0kthid));
				Coordinate mc1kth = new CasperQuery().getMaxDistanceCoordinate(c1, new CasperQuery().getGeometryById(c1kthid));
				if( /*c0kth.equals(c1kth)*/c0kthid == c1kthid || mc0kth.equals(mc1kth)) {
					//System.out.println("" + c0kthid + "-" + c1kthid + ", " + mc0kth + "-" + mc1kth + ", " + c0.distance(mc0kth) + ", " + c1.distance(mc1kth));
					if(!resultIds.contains(c0knnids)) {
						resultIds.add(c0kthid);
					}
					if(!resultIds.contains(c1knnids)) {
						resultIds.add(c1kthid);
					}
					//System.out.println("trivial edge");
				} else {
					//TODO: get furthest corner of the kth nn
					Coordinate c0kthp = mc0kth;
					Coordinate c1kthp = mc1kth;
					LineSegment l = new LineSegment(c0kthp, c1kthp);
					Coordinate c = CasperUtil.perpendicularBisector(l, segment);
					if(c != null) {
						if(elapsed + lineSegmentQueue.size() > t
								|| c.equals(c0) ||  c.equals(c1)) {
							// terminate search and return range search because cannot add more line segments to process
							// System.out.println("terminate with " + c + ", " + c0kthp + ", " + c.distance(c0kthp));
							if (improve) {
								resultIds.addAll(new CasperQuery().getAllPrivateDataID(c, c.distance(c0kthp) + 2 * epsilon, improve));
							} else {
								resultIds.addAll(new CasperQuery().getAllPrivateDataID(c, c.distance(c0kthp), improve));
							}
						} else {
							// add 2 segments
							// TODO: consider the order of c0, c, c1 ???
							lineSegmentQueue.add(new LineSegment(c0, c));
							lineSegmentQueue.add(new LineSegment(c, c1));
						}
					} else {
						if(!resultIds.contains(c0knnids)) {
							resultIds.add(c0kthid);
						}
						if(!resultIds.contains(c1knnids)) {
							resultIds.add(c1kthid);
						}
					}
				}
			}
			Set<Integer> hsc = new HashSet<Integer>();
			hsc.addAll(resultIds);
			resultIds.clear();
			resultIds.addAll(hsc);
			CasperProfiler.setAobjectCount(resultIds.size());
			tCasperSearch = System.currentTimeMillis();
			
			Coordinate best = null;
			double min = 999;
//			for(Coordinate c : exacts) {
//				double distance = Math.pow(c.x - exact.x, 2.0) + Math.pow(c.y - exact.y, 2.0);
//				////System.out.println(String.format("POINT: %f * %f : %f", c.x, c.y, distance));
//				if(distance < min) {
//					min = distance;
//					best = c;
//				}
//			}
			for (Integer id : resultIds) {
				//System.out.println(id);
			}
			
			//TODO: filter base on exact
			tFilter = System.currentTimeMillis();
			
			//System.out.println("total : " + resultIds.size());
		}
		
		long timeToOptimize = 0;//tOptimizePolygon - tStart;
		long timeToSearch = tCasperSearch - tStart;//tOptimizePolygon;
		long timeToFilter = tFilter - tCasperSearch;
		
		CasperProfiler.setAtotalTime(timeToOptimize + timeToSearch + timeToFilter);
		CasperProfiler.setAreduceTime(timeToOptimize);
		CasperProfiler.setAqueryTime(timeToSearch);
		CasperProfiler.setAfilterTime(timeToFilter);
		
//		System.out.println(String.format("Time: %d, %d, %d", timeToOptimize, timeToSearch, timeToFilter));
		
	}
	
	public static long evalRDP(double e, int n) {
		long o = 0;
		long c = 0;
		Geometry[] gs = new CasperDataGenerator().geometriesFromSeeds();
		for (int i = 0; i < n; i++) {
			int index = i % gs.length;
			
			Geometry g = gs[index];
			o += g.getCoordinates().length;
			Geometry s = CasperUtil.reduceGeometry(g, e);
			if (s.getCoordinates().length <= 3) {
				g = gs[0];
				s = CasperUtil.reduceGeometry(g, e);
			}
			c += s.getCoordinates().length;
//			System.out.println(index + ", " + g.getCoordinates().length + ", " + s.getCoordinates().length);
		}
		//System.out.println("" + e + ", " + o + ", " + c);
		System.out.println(c);
		return c;
	}
	
	public static void evalVRP(double e, boolean pub, boolean improve, int n) throws ParseException {
		int k = 1;
		int t = 208;
		Geometry[] gs = new CasperDataGenerator().geometriesFromSeeds();
		CasperProfiler.clear();
		System.out.println("exp = " + (pub ? "public " : "private ") + (improve ? "vrp " : "old ") + " " + e);
		for (int i = 0; i < n; i++) {
			Geometry g = gs[i % gs.length];
			String lineString = g.toText();
			//System.out.println((i + 1)+"/"+n + " : " + g.getCoordinates().length);
			t = g.getCoordinates().length * 1;
			new CasperSearch().search(new Coordinate(5, 5), lineString, k, t, pub, improve, e);
		}
		CasperProfiler.standardDeviation();
		System.out.println("max = " + CasperProfiler.getXtotalTime() + ", " + CasperProfiler.getXobjectCount());
		System.out.println("min = " + CasperProfiler.getStotalTime() + ", " + CasperProfiler.getSobjectCount());
		System.out.println("average = " + (CasperProfiler.getAtotalTime() / CasperProfiler.getN()) + ", " + (CasperProfiler.getAobjectCount() / CasperProfiler.getN()));
	}
	
	public static Rectangle rectFromEnvelope(Geometry e) {
		Coordinate c0 = e.getCoordinates()[0];
		Coordinate c2 = e.getCoordinates()[2];
		Rectangle r = new Rectangle((float)c0.x, (float)c0.y, (float)c2.x, (float)c2.y);
		return r;
	}
	
	public static Geometry envelopeFromRect(Rectangle r) {
		Coordinate c0 = new Coordinate(r.maxX, r.maxY);
		Coordinate c2 = new Coordinate(r.minX, r.minY);
		Coordinate c1 = new Coordinate(r.maxX, r.minY);
		Coordinate c3 = new Coordinate(r.minX, r.maxY);
		Coordinate[] cs = new Coordinate[5];
		cs[0] = c0;
		cs[1] = c1;
		cs[2] = c2;
		cs[3] = c3;
		cs[4] = c0;
		return new GeometryFactory().createLineString(cs);
	}
	
	public static double[] evalGEANodeWithoutGEA(List<Integer> ids) throws ParseException {
		double[] result = new double[2];
		int k = 1;
		int t = 208;
		Geometry[] gs = new CasperDataGenerator().geometriesFromSeeds();
		CasperProfiler.clear();
		for (int i : ids) {
			Geometry g = gs[i % gs.length];
			String lineString = g.toText();
			t = g.getCoordinates().length * 1;
			new CasperSearch().search(new Coordinate(5, 5), lineString, k, t, false, true, 0.0005);
		}
		//CasperProfiler.standardDeviation();
		double totaltime = CasperProfiler.getAtotalTime();
		double totalsize = CasperProfiler.getAobjectCount();
		result[0] = totaltime;
		result[1] = totalsize;
		return result;
	}
	
	public static double[] evalGEANodeWithGEA(List<Integer> ids) throws ParseException {
		double[] result = new double[2];
		int k = 1;
		int t = 208;
		Geometry[] gs = new CasperDataGenerator().geometriesFromSeeds();
		
		CasperProfiler.clear();
		Geometry ggea = gs[ids.get(0) % gs.length];
		for (int i : ids) {
			Geometry g = gs[i % gs.length];
			ggea = ggea.union(g);
		}
		ggea = ggea.convexHull();
		String lineString = ggea.toText();
		t = ggea.getCoordinates().length * 1;
		new CasperSearch().search(new Coordinate(5, 5), lineString, k, t, false, true, 0.0005);
		double geatime = CasperProfiler.getAtotalTime();
		double geasize = CasperProfiler.getAobjectCount();
//		System.out.println("gea = " + (CasperProfiler.getAtotalTime()) + ", " + (CasperProfiler.getAobjectCount()));
		result[0] = geatime;
		result[1] = geasize;
		return result;
	}
	
	public static void evalGEANodeSize(int n, List<Node> nodes, RTree rtree) throws ParseException {
		double totaltime = 0;
    	double totalsize = 0;
    	double totalgeatime = 0;
    	double totalgeasize = 0;
//    	System.out.println("======== GEA " + n + " ======== ");
		for (Node node : nodes) {
			List<Integer> leafIds = new LinkedList<Integer>();
	    	rtree.getLeavesOfNode(node, leafIds);
	    	
	    	if (leafIds.size() == n) {
//	    		double[] gea = evalGEANodeWithGEA(leafIds);
//	    		System.out.println(gea[0] + "," + gea[1]);
//	    		totalgeatime += gea[0];
//	    		totalgeasize += gea[1];
	    		double[] total = evalGEANodeWithoutGEA(leafIds);
//	    		System.out.println(total[0] + "," + total[1]);
	    		totaltime += total[0];
	    		totalsize += total[1];
	    		//return;
	    	}
		}
//		System.out.println(totalgeatime + "\t" + totalgeasize);
		System.out.println(totaltime + "\t" + totalsize);
//		System.out.println(totaltime + " => " + totalgeatime + " : " + (totaltime/totalgeatime));
//    	System.out.println(totalsize + " => " + totalgeasize + " : " + (totalsize/totalgeasize));
		//System.out.println("======== No GEA " + n + " ======== ");
	}
	
	public static void evalGEA(int n) throws ParseException {
		Properties p = new Properties();
	    p.setProperty("MinNodeEntries", Integer.toString(2));
	    p.setProperty("MaxNodeEntries", Integer.toString(10));
	    RTree rtree = (RTree) SpatialIndexFactory.newInstance("rtree.RTree", p);

	    Rectangle[] rects = new Rectangle[n];
	    Rectangle r = null;
	    // first add the rects
	    for (int i = 0; i < n; i++) {
	    	Geometry g = new CasperDataGenerator().geometriesFromSeed(i);
	    	Geometry gb = g.getEnvelope();
	    	r = rectFromEnvelope(gb);
	    	rtree.add(r, i);
	    }
	    
	    //rtree.debugLeaf();
	    
	    //System.out.println(rtree.size());
	    float rootArea = rtree.getNode(rtree.getRootNodeId()).getArea();
	    //System.out.println(rootArea);
	    //System.out.println(rootArea/r.area());
	    List<Node> nodes = new LinkedList<Node>();
	    rtree.getNodeWithMaxArea(r.area(), nodes);
	    //System.out.println(nodes.size());
	    int[] counts = new int[n];
	    for (Node node : nodes) {
	    	List<Integer> leafIds = new LinkedList<Integer>();
	    	rtree.getLeavesOfNode(node, leafIds);
//	    	Set<Integer> hsc = new HashSet<Integer>();
//			hsc.addAll(leafIds);
//			leafIds.clear();
//			leafIds.addAll(hsc);
//	    	System.out.println(leafIds);
//	    	evalGEANode(leafIds);
//	    	break;
//	    	for(int leafId : leafIds) {
//	    		counts[leafId]++;
//	    	}
	    }
	    evalGEANodeSize(2, nodes, rtree);
	    evalGEANodeSize(3, nodes, rtree);
	    evalGEANodeSize(4, nodes, rtree);
	    evalGEANodeSize(5, nodes, rtree);
	    evalGEANodeSize(6, nodes, rtree);
	    evalGEANodeSize(7, nodes, rtree);
	    evalGEANodeSize(8, nodes, rtree);
	    evalGEANodeSize(9, nodes, rtree);
	    evalGEANodeSize(10, nodes, rtree);
	    evalGEANodeSize(16, nodes, rtree);
//	    for (int i = 0; i < counts.length; i++) {
//	    	System.out.println(i + " : " + counts[i]);
//	    }
	    
	}

	/**
	 * @param args
	 * @throws ParseException 
	 */
	public static void main(String[] args) throws ParseException {
		
//		evalGEA(500);
		
		int n = 6400;
//		evalRDP(0.0010, n);
//		evalRDP(0.0009, n);
//		evalRDP(0.0008, n);
//		evalRDP(0.0007, n);
//		evalRDP(0.0006, n);
//		evalRDP(0.0005, n);
//		evalRDP(0.0004, n);
//		evalRDP(0.0003, n);
//		evalRDP(0.0002, n);
		evalRDP(0, n);
		evalRDP(0.0001, n);
		evalRDP(0.0002, n);
		evalRDP(0.0003, n);
		evalRDP(0.0004, n);
		evalRDP(0.0005, n);
//		evalRDP(0, n);
		
//		new CasperDataGenerator().distributeUniformPublicData(12100);
		
		
		// private is wrong right now, need to check the query
		int t = 10;
//		new CasperDataGenerator().distributeUniformPrivateData(n, 0.0005);
//		evalVRP(0.0005, false, false, t);
//		evalVRP(0.0005, false, true, t); //40 //27
//		new CasperDataGenerator().distributeUniformPrivateData(n, 0.0004);
//		evalVRP(0.0004, false, true, t); //31 //23 //19
//		new CasperDataGenerator().distributeUniformPrivateData(n, 0.0003);
//		evalVRP(0.0003, false, true, t); //30 //23 //18
//		new CasperDataGenerator().distributeUniformPrivateData(n, 0.0002);
//		evalVRP(0.0002, false, true, t); //29
//		new CasperDataGenerator().distributeUniformPrivateData(n, 0.0001);
//		evalVRP(0.0001, false, true, t); //28
//		new CasperDataGenerator().distributeUniformPrivateData(n, 0);
//		evalVRP(0, false, true, t); //12
		
//		int evrp = 100;
//		evalVRP(0.0001, true, true, evrp);
//		evalVRP(0.0002, true, true, evrp);
//		evalVRP(0.0003, true, true, evrp);
//		evalVRP(0.0004, true, true, evrp);
//		evalVRP(0.0005, true, true, evrp);
//		evalVRP(0.0005, true, false, evrp);
	}
/* old
SD Time = 399.60996346174596
SD Count = 50.668874211036375
max = 2146.0, 119
min = 61.0, 2
average = 548.415, 77

100
SD Time = 388.1796257123521
SD Count = 47.792675149857416
max = 2081.0, 119
min = 346.0, 2
average = 1205.82, 84
 */
/* 0.0005
SD Time = 230.90408032408249
SD Count = 37.60173632393295
max = 1850.0, 164
min = 49.0, 1
average = 287.128, 58

100
SD Time = 283.029868681128
SD Count = 31.54934406784255
max = 1406.0, 93
min = 54.0, 1
average = 298.25, 54
 */

/* 0.0001
100
SD Time = 342.3681720787217
SD Count = 49.54520841380809
max = 1637.0, 196
min = 29.0, 1
average = 430.88, 55
 */
	
/*
exp = public vrp  1.0E-4
SD Time = 59.07724777954036
SD Count = 4.838612525422051
max = 641.0, 35
min = 153.0, 5
average = 302.946, 13
exp = public vrp  2.0E-4
SD Time = 53.22879710120117
SD Count = 6.4325511046328785
max = 426.0, 55
min = 137.0, 5
average = 235.408, 15
exp = public vrp  3.0E-4
SD Time = 60.307002292648654
SD Count = 9.412253157958999
max = 479.0, 88
min = 100.0, 6
average = 211.847, 17
exp = public vrp  4.0E-4
SD Time = 58.00546996228494
SD Count = 11.478812575268321
max = 390.0, 88
min = 99.0, 7
average = 188.967, 19
exp = public vrp
SD Time = 76.13016786043794
SD Count = 15.040778704044044
max = 526.0, 128
min = 84.0, 6
average = 168.312, 23
exp = public old
SD Time = 79.45165930404578
SD Count = 4.783784651883444
max = 916.0, 34
min = 192.0, 5
average = 511.22, 13
 */
	
/*
 * exp = public vrp  1.0E-4
SD Time = 67.4565000125221
SD Count = 1.0854408404799487
max = 481.0, 7
min = 184.0, 1
average = 327.62, 3
exp = public vrp  2.0E-4
SD Time = 53.340850795955284
SD Count = 2.8122926410652735
max = 395.0, 27
min = 103.0, 1
average = 239.69, 3
exp = public vrp  3.0E-4
SD Time = 50.13465806599203
SD Count = 5.86841399598587
max = 337.0, 34
min = 102.0, 1
average = 205.53, 4
exp = public vrp  4.0E-4
SD Time = 50.41091251911734
SD Count = 14.903307883103867
max = 327.0, 98
min = 102.0, 1
average = 186.75, 7
exp = public vrp  5.0E-4
SD Time = 54.14236657725499
SD Count = 11.555207842476404
max = 304.0, 57
min = 79.0, 1
average = 180.09, 8
exp = public old  5.0E-4
SD Time = 77.58625047407736
SD Count = 1.0452393278695806
max = 715.0, 6
min = 380.0, 2
average = 545.1, 3
 */
	
/*
 * exp = public vrp  1.0E-4
SD Time = 75.50750849010872
SD Count = 1.122542208605543
max = 687.0, 8
min = 176.0, 1
average = 328.9, 3
exp = public vrp  2.0E-4
SD Time = 52.77672715594374
SD Count = 3.2645014469241223
max = 387.0, 30
min = 102.0, 1
average = 237.03, 3
exp = public vrp  3.0E-4
SD Time = 50.878409224862025
SD Count = 7.510121116993999
max = 337.0, 41
min = 102.0, 1
average = 207.56, 5
exp = public vrp  4.0E-4
SD Time = 51.2406985892971
SD Count = 17.33010168009479
max = 328.0, 110
min = 102.0, 1
average = 185.13, 8
exp = public vrp  5.0E-4
SD Time = 54.32859246838593
SD Count = 15.422324105272867
max = 313.0, 71
min = 79.0, 1
average = 186.0, 10
exp = public old  5.0E-4
SD Time = 79.76457531227426
SD Count = 1.0452393278695806
max = 722.0, 6
min = 383.0, 2
average = 559.42, 3
 */
	
	

}